Repository navigation
Android: custom Vulkan driver packages (Turnip) on Adreno - #19955
Open
Davey-Hughes wants to merge 2 commits into
Open
Davey-Hughes wants to merge 2 commits into
Davey-Hughes wants to merge 2 commits into
Conversation
This was referenced Oct 31, 2026
Sideloaded arm64 builds can load a Vulkan driver package (a zip with meta.json and one .so, such as Mesa's Turnip) on Adreno GPUs with Android 9 or later. As libadrenotools does, a private copy of the system Vulkan loader is opened in a linker namespace of its own, where a small hook library hands it the custom driver as its HAL. The new video_vulkan_driver_path setting names the zip; empty is the system driver, and core, content-directory and game overrides carry it. The package is checked and extracted to internal storage once per version. Packages from any vendor are accepted. Only Mesa ones have been tested, so any other vendor logs that it is untested and that its load cannot be confirmed. A Mesa package that leaves the stock Qualcomm driver running, as the hook does when the custom library fails to load, counts as failed. When the custom driver fails to create an instance, a device or the first swapchain, the context is redone on the system driver in the same process. A driver that takes the process down during its init is turned off for that core until it is picked again, and a "Safe start" launcher shortcut starts RetroArch once on the system driver. Play Store, Quest and API 16 builds do not carry any of it. Co-authored-by: lim2222 <johnlky222@gmail.com>
Settings > Video > Output gains a Custom Vulkan Driver row on builds and devices that can load one. OK opens a browser for driver zips in <system>/vulkan_drivers, Start goes back to the system driver, and the sublabel says when the chosen driver crashed during its last start and is off for the running core. While RetroArch runs from the Safe start shortcut, the row reads "System (Safe start)" and the sublabel names the driver the next normal start uses. The choice applies at the next Vulkan start; picking a driver never restarts video. Co-authored-by: lim2222 <johnlky222@gmail.com>
Davey-Hughes
force-pushed
the
android-custom-vulkan-driver
branch
from
November 1, 2026 20:06
aed6a3f to
00ca0bf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sideloaded arm64 Android builds can load a Vulkan driver package, such as Mesa's Turnip, on Adreno GPUs instead of the system driver. The main use is cores the stock driver renders badly. On an Adreno 740, for example, the stock driver draws stripe garbage for paraLLEl-RDP in Mupen64Plus-Next (SM64, OoT, MK64), and T30, a Turnip build, renders all three correctly.
Related feature requests: #15472, #18143.
Commits
video_vulkan_driver_path, that names a zip holdingmeta.jsonand one.so. An empty value means the system driver, and overrides carry it. The package is checked and extracted to internal storage once per version. This commit also adds the fallbacks, the crash state and the Safe start shortcut. The package logic is platform-free (gfx/common/vulkan_custom_driver.c), and a unit sample and a CI job cover it.<system>/vulkan_drivers, and Start goes back to the system driver. The sublabel says when the chosen driver crashed. A new choice takes effect at the next Vulkan start, and picking one never restarts video.The driver loading is a C port of libadrenotools' technique, in
gfx/common/vulkan_custom_driver_linker.c, which keeps Billy Laws' BSD-2-Clause notice. A private copy of the system Vulkan loader is opened in a linker namespace of its own, where a small hook library hands it the custom driver. The file is built intolibretroarch-activity.soand, a second time, as that hook,libvulkan_driver_hook.so. There is no C++ and nodeps/tree.No driver is bundled. Users download a package (for example a Turnip build, in the same zip format the AdrenoTools-based emulators load) and copy it to
<system>/vulkan_drivers/. If wanted, an online updater source can follow in a later PR.Gating
HAVE_VULKAN_CUSTOM_DRIVER. The API 16 build passesHAVE_VULKAN_CUSTOM_DRIVER=0..sofiles) from outside Play. That is why the Play builds already get cores through Play Feature Delivery. The Quest build is also a Play build (PLAY_STORE_BUILD=1). On Quest, the OpenXR runtime also does swapchain interop on RetroArch'sVkDevice, which nobody has tested with Turnip.Android.mksetsHAVE_VULKAN_CUSTOM_DRIVERonly when neitherPLAY_STORE_BUILDnorHAVE_OPENXRis set, and the Play and Quest APKs carry no custom driver symbols./dev/kgsl-3d0) and Android 9 (API 28) or later. Elsewhere the row is hidden, and a synced-in value is ignored with an OSD message.dlopen's code page to find the linker's internaldlopen, runs only when a custom driver is first opened, andlibvulkan_driver_hook.soloads only then. Neither happens for users who don't pick a driver.Fallbacks and crash state
meta.json,.socount, not arm64 ELF, over 64 MiB, missing)Each case shows an OSD message once per path per session. A crash after the first swapchain is not caught. For that, the user presses Start on the row or uses Safe start.
Safe start is a launcher shortcut (Android 7.1+), added before the first custom load. It starts RetroArch once on the system driver, restarting the process if needed, and leaves the saved value alone. Meanwhile the row reads "System (Safe start)".
Vendor policy (please weigh in)
Packages from any vendor load. Only Mesa Turnip was tested. The non-Mesa path was checked with a Turnip build relabelled
"vendor": "Qualcomm".How it works:
vendoris required inmeta.json. A package counts as Mesa when it says "Mesa" (any case).Why: Qualcomm driver repacks are widely used with other Android emulators, so refusing them outright would turn those users away. But a repack reports the same driverID as the stock driver. The Mesa check would mark every repack as failed, because the driverID can't tell a working repack from a silent fallback. We also considered comparing against the stock driver's version, saved from an earlier system-driver run. That needs a prior run and more saved state, and it misfires when a repack has the same version as the stock driver. So other vendors load unverified, and the log says so rather than claiming the custom driver is running.
Qualcomm repacks are proprietary blobs taken from other devices. If you'd rather not load them, Mesa-only is a small change to the package check.
Known issues
Trying it
The one case found so far that the stock driver gets wrong and a custom driver fixes is paraLLEl-RDP in Mupen64Plus-Next. To see it on an Adreno 740 (tested on the AYN Thor, stock driver 0676.53):
turnip_mrpurple_T30-toasted.adpkg.zipfrom T30's release and copy it, still zipped, to<system>/vulkan_drivers/. T30'smeta.jsonasks for Android 11 (API 30) or later.vulkan. In Mupen64Plus-Next's core options, setmupen64plus-rdp-pluginandmupen64plus-rsp-plugintoparallel.AdrenoVK-0shader compile and pipeline-create failures.[Vulkan] Using GPU #0line namesPurpleVK-public Adreno (TM) 740instead of the stockAdreno (TM) 740.Not every Turnip build fixes it. K11MCH1's v26.0.0 R8 lacks the 8/16-bit storage paraLLEl-RDP needs on this GPU: Mupen64Plus-Next crashes in
retro_run, and parallel_n64, which renders on the stock driver, goes black. Older Adreno GPUs such as the Adreno 650 in #18143 lack that storage on the stock driver too, and paraLLEl-RDP shows its error screen there. Whether a Turnip build fixes those GPUs was not tested.Beetle PSX HW, SwanStation, Azahar, Dolphin, Flycast, PPSSPP and parallel_n64 rendered the same on T30 and on the stock driver, so they work as regression checks but don't need a custom driver.
Testing
samples/gfx/vulkan_custom_driver: 352 checks covering the package checks,meta.json, the crash state and session limits. It passes on Linux under ASan+UBSan, and in a mingw-w64 build under wine. The newvulkan-custom-driverCI job runs it.libvulkan_driver_hook.so.C89_BUILD=1on touched objects and--disable-threadsboth pass. Local runs of the covering CI jobs fail only where master does.AYN Thor (Adreno 740, Android 13), with T30 and "Mesa Turnip driver v26.0.0 - R8". The comparison, the bad packages, pruning and Safe start through its intent extra were rerun on this branch's final commits, with the same results as on the previous push (aed6a3f). The switching, closing-content and Safe start UI checks ran on an earlier build, before the final review fixes and the C port.
.so(no GPU);"vendor": "Qualcomm"loads and runs, logging both warnings once per init.Tested on Android 13 (AYN Thor, Adreno 740). Android 9 takes a different path (the loader copy goes through a temporary file instead of a memfd); it would be good if someone with an Android 9 Adreno device could test it.
Credit
Built from lim2222's proof of concept, https://github.com/lim2222/RetroArch-Turnip (branch
feature/android-turnip-driver). lim2222 is co-author of both commits.